package cn.fulong.sso.client.utils;

import cn.fulong.sso.client.request.SSOAbstractApiRequest;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Set;
import java.util.UUID;

/**
 * @Version 1.0
 * @Author: 云树
 * @Description:
 * @Date: 2019/3/12
 * @Modify 云树
 * @ModifyDate 2019/3/12
 * @since 1.0
 */
public class SSOSignUtils {

    public static final String FIELD_SIGN = "sign";


    /**
     * @param obj
     * @param key
     * @return 验证签名
     * @throws Exception
     */
    public static boolean isSignatureValid(SSOAbstractApiRequest obj, String key) throws Exception {
        SSOHashMap data = bean2SSOHashMap(obj);
        return isSignatureValid(data, key);
    }

    /**
     * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(SSOHashMap data, String key) throws Exception {
        String sign = data.get(FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 生成签名.
     *
     * @param obj 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final SSOAbstractApiRequest obj, String key) throws Exception {
        SSOHashMap data = bean2SSOHashMap(obj);
        return generateSignature(data, key);
    }

    /**
     * 生成签名.
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public static String generateSignature(final SSOHashMap data, String key) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(FIELD_SIGN)) {
                continue;
            }
            String val = data.get(k);
            // 参数值为空，则不参与签名
            if (StringSelfUtil.isNotBlank(val)) {
                sb.append(k).append("=").append(val).append("&");
            }
        }
        sb.append("key=").append(key);
        return MD5(sb.toString()).toUpperCase();
    }

    /**
     * @return api请求实体转为Map，做生成签名用 <String,String>
     */
    public static SSOHashMap bean2SSOHashMap(SSOAbstractApiRequest obj) throws Exception {
        if (obj == null) {
            return null;
        }
        SSOHashMap map = new SSOHashMap();
        BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor property : propertyDescriptors) {
            String key = property.getName();
            // 得到property对应的getter方法
            Method getter = property.getReadMethod();
            Object value = getter.invoke(obj);
            map.put(key, value);
        }
        return map;
    }

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        return generateUUID();
    }

    /**
     * 获取当前时间戳，单位毫秒
     *
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

    /**
     * 生成 uuid
     *
     * @return
     */
    public static String generateUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }

    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

}
